#!/usr/bin/env python
#-*- coding:utf-8 -*-


from pyQueue import pyQ
from xml.dom import Node
import gobject, xml.dom.minidom as minidom

class IEDRoom(object):
	'''Model for a chat room'''
	name = ''
	id = 0
	
	def __init__(self, room_id, room_name):
		self.id, self.name = room_id, room_name
	
	def __unicode__(self):
		return self.name
	
	def __str__(self):
		return self.name

class IEDUser(object):
	'''Model for a chat user'''
	name = ''
	id = 0
	room = None
	current_status = 0
	room_avatar = '' # Room Avatar (the one displayed in the room list)
	main_avatar = '' # Main Chat Avatar (the one displayed in the main window)
	
	STATUS_HERE, STATUS_BUSY, STATUS_AWAY, STATUS_BRB = 1, 2, 3, 4
	status = {STATUS_HERE: 'Here', STATUS_BUSY: 'Busy', STATUS_AWAY: 'Away', STATUS_BRB: 'BRB'}
	
	def __init__(self, user_id, user_name, user_room):
		self.name, self.id, self.room = user_name, user_id, user_room
		self.current_status = self.STATUS_HERE
	
	def __unicode__(self):
		return self.name
	
	def name_and_status(self):
		'''Returns a representation of the user name that includes the status (if it is different from "here")'''
		if self.current_status == self.STATUS_HERE:
			return self.name
		else:
			return '[%s] %s' % (self.status.get(self.current_status), self.name)

class IEDChat(object):
	'''Model for the chat itself'''
	
	users = {} # Associates user ids with corresponding IEDUser objects
	rooms = {} # Associates room ids with IEDRoom objects
	
	current_user = None # Pointer to the current user
	current_room = None # Pointer to the current room
	
	is_working = False # Boolean that indicates if the instance is processing a request
	
	mainloop_timeout_id = 0 # Return value of the gobject.idle_add() call
	
	ui = None # A UI object with a specific set of method used to update the UI
	
	send_queue = pyQ() # Queue of actions left to send to the server
	
	available_avatars = ['admin', 'ask', 'AustraliaFlag', 'awe', 'baby', 'ball', 'basketball', 'beer', 'big_grin', 'bowling', 'Brazil', 'call', 'CanadaFlag', 'cash', 'check', 'China', 'clap', 'cool', 'cricket', 'cry', 'earth', 'European_Union', 'evil', 'faint', 'female2', 'female', 'finger', 'football', 'France', 'Germany', 'golf', 'Greece', 'grin', 'heart', 'hockey', 'huh', 'hypno', 'ill_content', 'IndianFlag', 'Italy', 'Japan', 'java', 'kiss', 'laugh', 'luck', 'male2', 'male', 'meow', 'MexicoFlag', 'moderator', 'music', 'newline', 'ninja', 'no', 'nono', 'PolandFlag', 'PortugalFlag', 'punch', 'rain', 'reading', 'red', 'roll_eyes', 'roll', 'rose', 'Russia', 'sad', 'sailing', 'shock2', 'shock', 'skull', 'slash', 'sleep', 'smile', 'soccer', 'Spain', 'Sweeden', 'tennis', 'thumbs_down', 'thumbs_up', 'tongue', 'UK', 'UkraineFlag', 'usa', 'US_Map', 'weird', 'whistle', 'wink', 'wonder', 'woof', 'word_bubble', 'yeah', 'yinyang'] # List of available avatars
	
	blocked_words = {
		'asses': 'ässes',
		'asshole': 'ässhole',
		'bitch': 'bïtch',
		'blowjob': 'blöwjob',
		'clit': 'clït',
		'cock': 'cöck',
		'cum': 'cüm',
		'cunt': 'cünt',
		'damn ': 'dämn',
		'dildo': 'dïldo',
		'dink': 'dïnk',
		'fuck': 'fück',
		'fuk': 'fük',
		'jism': 'jïsm',
		'jiz ': 'jïz',
		'kock': 'köck',
		'kum': 'küm',
		'nigger': 'nïgger',
		'phuk': 'phük',
		'phuq': 'phüq',
		'shit': 'shït',
		'spunk': 'spünk',
		'twat': 'twät'
	}
	
	MIN_WAIT, NORMAL_WAIT, MAX_WAIT = 200, 3000, 15000 # delays for the gobject.idle_add() call
	
	def __init__(self, proxy, ui):
		'''Creates an instance of the chat object, using the given proxy for the conection'''
		self.proxy, self.ui = proxy, ui
		self.ui.chat = self # I'm not sure if that's a bad idea or not...
		
		#Init ui user status list
		self.ui.add_user_statuses([(s, IEDUser.status.get(s)) for s in IEDUser.status])
		self.ui.wtree.get_widget('status_select').set_active(0)
		
		#Init ui avatar list
		self.ui.add_avatars(self.available_avatars)
	
	def connect(self, username, password):
		'''Starts the proxy connection and pushes the first action in the queue'''
		if self.proxy.login(username, password):
			self.send_queue.flush()
			self.send_queue.push({'c':'tzset', 'cid':'1', 'tz':'-60'})
			return True
		
		return False
	
	def disconnect(self):
		'''Destroys pending timeouts and logs out the proxy'''
		gobject.source_remove(self.mainloop_timeout_id)
		self.proxy.logout()
		return True
	
	def mainloop(self):
		'''Main loop. Needs to return False'''
		if self.is_working:
			self.mainloop_timeout_id = gobject.timeout_add(self.MIN_WAIT, self.mainloop)
			return
		
		self.is_working = True
		action = self.send_queue.pop()
		if action:
			delay = self.MIN_WAIT
		else:
			action = {'c': 'msgl'}
			if self.current_user.status == IEDUser.STATUS_AWAY:
				delay = self.MAX_WAIT
			else:
				delay = self.NORMAL_WAIT
		
		gobject.idle_add(self.proxy.request, action, self.handle_xml_response) # Non blocking call (asynchronous)
		self.mainloop_timeout_id = gobject.timeout_add(delay, self.mainloop)
		
		return False # Needed to stop gobject.timeout_add from repeating the function call
	
	def handle_xml_response(self, xml):
		'''Handles the server xml responses and dispatches actions to the UI object'''
		try:
			response = minidom.parseString(xml).documentElement
		except:
			print 'Hum hum', xml
			return False
		
		self.proxy.communication_id = response.attributes['id'].value
		for event_node in response.childNodes:
			if event_node.nodeType is not Node.TEXT_NODE:
				self.proxy.last_msg_id = int(event_node.attributes['id'].value)
				self.handle_action(event_node)
		
		self.is_working = False
	
	def handle_action(self, action_node):
		'''Handles an individual action and dispatch it to the UI object if necessary'''
		
		action = action_node.nodeName.lower()
		
		if action == 'msg':
			# New message arrived
			user_id = int(action_node.attributes['u'].value)
			text = action_node.firstChild.nodeValue
			sender = self.users.get(user_id)
			if sender:
				self.ui.message_received(sender, text)
			else:
				# Duh! Message from an unknown user...
				#TODO: handle this case
				pass
			return True
		
		if action == 'msgu':
			# New urgent message arrived
			# TODO: factor the code with the "msg" signal
			user_id = int(action_node.attributes['u'].value)
			text = action_node.firstChild.nodeValue
			sender = self.users.get(user_id)
			if sender:
				self.ui.message_received(sender, text)
			else:
				# Duh! Message from an unknown user...
				#TODO: handle this case
				pass
			return True
		
		if action == 'adu':
			# Inserting the current user
			user_id = int(action_node.attributes['u'].value)
			room_id = int(action_node.attributes['r'].value)
			user_name = action_node.firstChild.nodeValue
			
			u = IEDUser(user_id, user_name, self.rooms.get(room_id))
			r = self.rooms.get(room_id)
			self.users[user_id] = u
			self.current_user, self.current_room = u, r
			self.ui.add_user(u)
			return True
		
		if action == 'adu2':
			# Inserting a new user
			user_id = int(action_node.attributes['u'].value)
			room_id = int(action_node.attributes['r'].value)
			user_name = action_node.firstChild.nodeValue
			
			u = IEDUser(user_id, user_name, self.rooms.get(room_id))
			self.users[user_id] = u
			self.ui.add_user(u)
			return True
		
		if action == 'rmu':
			# Removing a user
			user_id = int(action_node.attributes['u'].value)
			u = self.users.get(user_id)
			
			if u:
				self.ui.delete_user(u)
				del self.users[user_id], u
			else:
				#TODO: there seem to be a bug where this condition is realised. I'd like to know why...
				pass
			return True
		
		if action == 'adr':
			# New room
			room_id = int(action_node.attributes['r'].value)
			room_name = action_node.firstChild.nodeValue
			
			r = IEDRoom(room_id, room_name)
			self.rooms[room_id] = r
			self.ui.add_room(r)
			return True
		
		if action == 'lin':
			#Log in (the first received message)
			# From this action, we can theoretically get the current room id and user id. however, these would not be very useful as the corresponding object have not been created, and this information will be given again later.
			# current_room_id = int(action_node.attributes['r'].value)
			# current_user_id = int(action_node.attributes['u'].value)
			return True
		
		if action == 'ravt':
			# Change room avatar for a user
			user_id = int(action_node.attributes['u'].value)
			try:
				avatar = action_node.firstChild.nodeValue
			except AttributeError:
				avatar = ''
			u = self.users.get(user_id)
			
			if u:
				u.room_avatar = avatar
				self.ui.room_avatar_changed(u)
			return True
		
		if action == 'mavt':
			# Change main chat avatar for a user
			user_id = int(action_node.attributes['u'].value)
			try:
				avatar = action_node.firstChild.nodeValue
			except AttributeError:
				avatar = ''
			u = self.users.get(user_id)
			
			if u:
				u.main_avatar = avatar
				self.ui.main_avatar_changed(u)
			return True
		
		if action == 'ustc':
			# Change the status of a user
			user_id = int(action_node.attributes['u'].value)
			new_status = int(action_node.firstChild.nodeValue)
			u = self.users.get(user_id)
			
			if u:
				u.current_status = new_status
				self.ui.user_status_changed(u)
			return True
		
		if action == 'mvu':
			# Change room
			user_id = int(action_node.attributes['u'].value)
			room_id = int(action_node.attributes['r'].value)
			u = self.users.get(user_id)
			r = self.rooms.get(room_id)
			
			if u and r:
				u.room = r
				if u is self.current_user:
					self.current_room = r
				self.ui.user_changed_room(u)
			return True
		
		if action == 'uclc':
			# Change the color used for a user in the room list
			#TODO: take it into account (...or not)
			return True
		
		if action == 'lng':
			# A huge big xml string that contains the texts used for the interface
			#TODO: extract useful info from this (like status text)
			return True
		
		if action == 'lout':
			#Log out
			# TODO: handle logout more elegantly
			print 'You have been logged out'
			sys.exit(1)
		
		# If we are here, then the response is unknown
		print 'Un-handled response : %s' % action_node.toprettyxml()
		return True
	
	def send_message(self, msg):
		'''Sends a [msg] request to the server'''
		self.send_queue.push({'c': 'msg', 'r': self.current_room.id, 'u': 0, 't': msg})
		self.send_queue.push({'c': 'msgl'}) # Always ask for a message list after a message is sent
		gobject.source_remove(self.mainloop_timeout_id)
		gobject.idle_add(self.mainloop)
	
	def request_change_avatar(self, avatar):
		'''Sends a [mavt] and a [ravt] request to the server'''
		self.send_queue.push({'c': 'ravt', 'a': avatar, 'u': 'null'})
		self.send_queue.push({'c': 'mavt', 'a': avatar, 'u': 'null'})
	
	def request_change_room(self, room):
		'''Sends a [mvu] request to the server'''
		if room is not self.current_room:
			self.send_queue.push({'c': 'mvu', 'r': room.id})
	
	def request_change_status(self, status):
		'''Sends a [sst] request to the server'''
		self.send_queue.push({'c': 'sst', 't': status})
